package com.nativetalk.common.filter;

import com.nativetalk.base.RetInfo;
import com.nativetalk.bean.SystemAccessLog;
import com.nativetalk.bean.member.TdNtMember;
import com.nativetalk.bean.teacher.TdNtTeacherResult;
import com.nativetalk.common.Lang;
import com.nativetalk.common.util.Ip;
import com.nativetalk.common.util.JsonUtil;
import com.nativetalk.common.util.MD5;
import com.nativetalk.common.util.MemcachedUtils;
import com.nativetalk.dao.SystemAccessLogDao;
import com.nativetalk.dao.TdNtMemberDao;
import com.nativetalk.dao.TdNtTeacherDao;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.util.*;


/**
 * 执行token过滤器
 */
@Component
public class SignFilter implements Filter {
    @Resource
    TdNtMemberDao tdNtMemberDao;
    @Resource
    TdNtTeacherDao tdNtTeacherDao;
    @Autowired
    SystemAccessLogDao systemAccessLogDao;

    private static Logger logger = Logger.getLogger(SignFilter.class);

    public void destroy() {
    }

    @SuppressWarnings("unchecked")
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        String logInfo = SignFilter.class + ":doFilter:";
        // 类路径+方法名
        logger.info("======" + logInfo + "begin======");
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        try {
            printHttpHeader(request);

            String uri = request.getRequestURL().toString();
            if (uri.indexOf("JspPage") > 0) {//web页面请求】
                logger.info("------------ 页面请求 -----------");
            } else {
                // 非法字符校验
//            if (!ahCheckInjectPass(request, response)) {
//                errorResponse(response);
//                logger.info("====== end ======\n");
//                return;
//            }
                response.setCharacterEncoding("UTF-8");
                response.setContentType("text/html; charset=UTF-8");

                String pathInfo = request.getPathInfo() == null ? "" : request.getPathInfo();
                String url = request.getServletPath() + pathInfo;
                logger.info("url:" + url);

                if (url.indexOf("/app/1") > 0) {
                    //判断链接版本号
                    String version = request.getHeader("version");
                    try {
                        int versionInt = Integer.valueOf(version);
                        if (versionInt >= 1) {
                            String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                            logger.info(tip);
                            PrintWriter out = response.getWriter();
                            out.println(tip);
                            out.flush();
                            out.close();
                            logger.info("====== end ======\n");
                            return;
                        }
                    } catch (Exception e) {
                        String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                        logger.error(tip, e);
                        PrintWriter out = response.getWriter();
                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    }
                }

                if (url.indexOf("/app/6") > 0) {
                    //判断链接版本号
                    String version = request.getHeader("version");
                    try {
                        int versionInt = Integer.valueOf(version);
                        if (versionInt >= 6) {
                            String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                            logger.info(tip);
                            PrintWriter out = response.getWriter();
                            out.println(tip);
                            out.flush();
                            out.close();
                            logger.info("====== end ======\n");
                            return;
                        }
                    } catch (Exception e) {
                        String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                        logger.error(tip, e);
                        PrintWriter out = response.getWriter();
                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    }
                }
                if (url.indexOf("/app/7") > 0) {
                    //判断链接版本号
                    String version = request.getHeader("version");
                    try {
                        int versionInt = Integer.valueOf(version);
                        if (versionInt >= 7) {
                            String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                            logger.info(tip);
                            PrintWriter out = response.getWriter();
                            out.println(tip);
                            out.flush();
                            out.close();
                            logger.info("====== end ======\n");
                            return;
                        }
                    } catch (Exception e) {
                        String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                        logger.error(tip, e);
                        PrintWriter out = response.getWriter();
                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    }
                }
                if (url.indexOf("/app/5") > 0) {
                    //判断链接版本号
                    String version = request.getHeader("version");
                    try {
                        int versionInt = Integer.valueOf(version);
                        if (versionInt >= 5) {
                            String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                            logger.info(tip);
                            PrintWriter out = response.getWriter();
                            out.println(tip);
                            out.flush();
                            out.close();
                            logger.info("====== end ======\n");
                            return;
                        }
                    } catch (Exception e) {
                        String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                        logger.error(tip, e);
                        PrintWriter out = response.getWriter();
                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    }
                }

                if (url.indexOf("/app/2") > 0) {
                    //判断链接版本号
                    String version = request.getHeader("version");
                    try {
                        int versionInt = Integer.valueOf(version);
                        if (versionInt >= 2) {
                            String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                            logger.info(tip);
                            PrintWriter out = response.getWriter();
                            out.println(tip);
                            out.flush();
                            out.close();
                            logger.info("====== end ======\n");
                            return;
                        }
                    } catch (Exception e) {
                        String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                        logger.error(tip, e);
                        PrintWriter out = response.getWriter();
                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    }
                }
                if (url.indexOf("/app/3") > 0) {
                    //判断链接版本号
                    String version = request.getHeader("version");
                    try {
                        int versionInt = Integer.valueOf(version);
                        if (versionInt >= 4) {
                            String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                            logger.info(tip);
                            PrintWriter out = response.getWriter();
                            out.println(tip);
                            out.flush();
                            out.close();
                            logger.info("====== end ======\n");
                            return;
                        }
                    } catch (Exception e) {
                        String tip = "{\"tip\":" + "\"版本号有误\"" + ",\"mark\":\"400\"}";
                        logger.error(tip, e);
                        PrintWriter out = response.getWriter();
                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    }
                }
                //判断客户端类型是否有误1 android ; 2 ios
                String client_type = request.getHeader("client_type");
                if (!("2".equals(client_type) || "1".equals(client_type) || "3".equals(client_type) || "4".equals(client_type))) {
                    String tip = "{\"tip\":" + "\"客户端类型有误\"" + ",\"mark\":\"401\"}";
                    logger.info(tip);
                    PrintWriter out = response.getWriter();

                    out.println(tip);
                    out.flush();
                    out.close();
                    logger.info("====== end ======\n");
                    return;
                }
                //判断是否恶意刷屏 同一个IP2小时之内访问次数不能超过10W次
                Map<String, Integer> map = new HashMap<>();
                String ip = Ip.getIpAddr(request);
                if (MemcachedUtils.get(ip) != null) {
                    map = (Map<String, Integer>) MemcachedUtils.get(ip);
                    int size = map.get("size") + 1;
                    logger.info("The ip " + ip + " request number of times:" + size);
                    if (size >= 100000) {
                        String tip = "{\"tip\":" + "\"请求过于频繁\"" + ",\"mark\":\"600\"}";
                        logger.info("The ip " + ip + " request number of times:" + size + " tip：" + tip);
                        PrintWriter out = response.getWriter();

                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    } else {
                        map.put("size", size);
                        MemcachedUtils.replace(ip, map, 2 * 60 * 60);
                    }
                } else {
                    map.put("size", 1);
                    logger.info("The ip " + ip + " request number of times:" + 1);
                    MemcachedUtils.add(ip, map, 2 * 60 * 60);
                }

                if (url.indexOf(".") == -1) {
                    String Key = "nativetalk";
                    String Timestamp = request.getHeader("Timestamp");
                    String SignInfo = request.getHeader("SignInfo");
                    String code = Timestamp + Key;
                    //判断时间戳和加密信息是否为空
                    if (Timestamp != null && SignInfo != null) {
                        Long s = (System.currentTimeMillis() / 1000 - Long.valueOf(Timestamp)) / 60;//单位为分钟
                        //判断时间戳是否过期  时长5分钟
                        if (s <= 5 * 12 * 24 * 30) {//测试时间加长：30天
                            //判断加密信息是否正确
                            if (!SignInfo.equals(MD5.code(code))) {
                                String tip = "{\"tip\":" + "\"签名已超时\"" + ",\"mark\":\"500\"}";
                                logger.info(tip);
                                PrintWriter out = response.getWriter();

                                out.println(tip);
                                out.flush();
                                out.close();
                                logger.info("====== end ======\n");
                                return;
                            } else {
                                if (url.indexOf("/teatoken") > 0) {
                                    //判断token是否为空
                                    String token = request.getHeader("token");
                                    if (StringUtils.isBlank(token)) {
                                        //没有token字段，让用户重新登录，ios客户端如果拿到token会带上token
                                        errorResponse(response, RetInfo.getReloginInfo(Lang.EN));
                                        logger.info("====== end ======\n");
                                        return;
                                    }

                                    Object o_teacher = MemcachedUtils.get(token);
                                    TdNtTeacherResult tdNtTeacher = null;
                                    //如果缓存中没有token，那么需要查询数据库
                                    if (o_teacher == null) {
                                        //从数据库中查询用户信息
                                        tdNtTeacher = tdNtTeacherDao.findTdNtTeacherByToken(token);
                                        //如果缓存和数据库中都没有该token对应的用户，那么用户需要用户重新登录系统
                                        if (tdNtTeacher == null) {
                                            errorResponse(response, RetInfo.getReloginInfo(Lang.EN));
                                            logger.info("====== end ======\n");
                                            return;
                                        }//缓存中不存在，但是数据库中存在，将数据库中的token加入缓存
                                        else {
                                            MemcachedUtils.add(tdNtTeacher.getToken(), tdNtTeacher, 20 * 24 * 60 * 60);
                                        }
                                    }
                                    //如果缓存中有token，那么需要做类型检测，如果类型不是 TdNtTeacherResult，那么仍然需要用户从新登陆系统。
                                    //为什么根据token从缓存中查出的数据不是TdNtMember类型？这种可能性很小，比如更换缓存客户端，而他们使用的序列化机制有不一样，那么就会有类型不一致的问题。
                                    else {
                                        if (!TdNtTeacherResult.class.isInstance(o_teacher)) {
                                            RetInfo retInfo = RetInfo.getReloginInfo(Lang.EN);
                                            logger.info("缓存的token不是TdNtTeacherResult类型:[" + token + "]，用户需要重新登录系统。 retInfo-->" + retInfo);
                                            errorResponse(response, RetInfo.getReloginInfo(Lang.EN));
                                            logger.info("====== end ======\n");
                                            return;
                                        } else {
                                            tdNtTeacher = (TdNtTeacherResult) o_teacher;
                                            MemcachedUtils.replace(tdNtTeacher.getToken(), tdNtTeacher, 20 * 24 * 60 * 60);
                                        }
                                    }
                                }

                                //学生端token校验
                                if (url.indexOf("/token") > 0) {
                                    //判断token是否为空
                                    String token = request.getHeader("token");
                                    if (StringUtils.isBlank(token)) {
                                        //没有token字段，让用户重新登录，ios客户端如果拿到token会带上token
                                        errorResponse(response, RetInfo.getReloginInfo(Lang.ZH));
                                        logger.info("====== end ======\n");
                                        return;
                                    }

                                    Object o_member = MemcachedUtils.get(token);
                                    TdNtMember tdNtMember = null;
                                    //如果缓存中没有token，那么需要查询数据库
                                    if (o_member == null) {
                                        //从数据库中查询用户信息
                                        tdNtMember = tdNtMemberDao.findTdNtMemberByToken(token);
                                        //如果缓存和数据库中都没有该token对应的用户，那么用户需要用户重新登录系统
                                        if (tdNtMember == null) {
                                            errorResponse(response, RetInfo.getReloginInfo(Lang.ZH));
                                            logger.info("====== end ======\n");
                                            return;
                                        }
                                        //如果缓存中不存在但是数据库中存在，将数据库中的用户信息加入缓存（应为有可能缓存因重启导致丢失），这里会有一个bug导致20天未登录的用户仍不需要登录
                                        else {
                                            MemcachedUtils.add(tdNtMember.getToken(), tdNtMember, 20 * 24 * 60 * 60);
                                        }
                                    }
                                    //如果缓存中有token，那么需要做类型检测，如果类型不是 TdNtMember，那么仍然需要用户重新登陆系统。
                                    //为什么根据token从缓存中查出的数据不是TdNtMember类型？这种可能性很小，比如更换缓存客户端，而他们使用的序列化机制不一样时，那么就会有类型不一致的问题。
                                    else {
                                        if (!TdNtMember.class.isInstance(o_member)) {
                                            RetInfo retInfo = RetInfo.getReloginInfo(Lang.EN);
                                            logger.info("缓存的token不是TdNtMember类型:[" + token + "]，用户需要重新登录系统。 retInfo-->" + retInfo);
                                            errorResponse(response, RetInfo.getReloginInfo(Lang.ZH));
                                            logger.info("====== end ======\n");
                                            return;
                                        } else {
                                            //最后更新缓存中的用户数据，过期时间从新设置为20天，即：用户20天未进入app则需要重新登录系统
                                            tdNtMember = (TdNtMember) o_member;
                                            MemcachedUtils.replace(tdNtMember.getToken(), tdNtMember, 20 * 24 * 60 * 60);
                                        }

                                    }

                                }
                            }
                        } else {
                            String tip = "{\"tip\":" + "\"签名无效\"" + ",\"mark\":\"501\",\"obj\":" + "\"null\"}";
                            PrintWriter out = response.getWriter();
                            logger.info(tip);
                            out.println(tip);
                            out.flush();
                            out.close();
                            logger.info("====== end ======\n");
                            return;

                        }
                    } else {
                        String tip = "{\"tip\":" + "\"签名参数有误\"" + ",\"mark\":\"502\",\"obj\":" + "\"null\"}";
                        logger.info(tip);
                        PrintWriter out = response.getWriter();

                        out.println(tip);
                        out.flush();
                        out.close();
                        logger.info("====== end ======\n");
                        return;
                    }
                    logger.info("token filter >>>> success  ");
                }
            }
        } catch (IOException e) {
            String tip = "{\"tip\":" + "\"系统异常\"" + ",\"mark\":\"500\",\"obj\":" + "\"null\"}";
            logger.error("tip:" + tip + "  " + e.getMessage(), e);
            PrintWriter out = response.getWriter();

            out.println(tip);
            out.flush();
            out.close();
        } catch (NumberFormatException e) {
            String tip = "{\"tip\":" + "\"系统异常\"" + ",\"mark\":\"500\",\"obj\":" + "\"null\"}";
            logger.error("tip:" + tip + "  " + e.getMessage(), e);
            PrintWriter out = response.getWriter();

            out.println(tip);
            out.flush();
            out.close();
        }

        try {
            chain.doFilter(req, res);
        } catch (Exception e) {
            String tip = "{\"tip\":" + "\"系统异常\"" + ",\"mark\":\"500\",\"obj\":" + "null\"}";
            logger.error("tip:" + tip + "  " + e.getMessage(), e);
            PrintWriter out = response.getWriter();
            out.println(tip);
            out.flush();
            out.close();
        }
        logger.info("====== end ======\n");
    }

    protected boolean ahCheckInjectPass(HttpServletRequest req, HttpServletResponse response) throws IOException {
        //1、POST模式非法字符定义
        //双斜杠加上后，会影响被动登录
        String inj_str = "(function(,script,iframe,insert,delete,update,declare,expression,XSS,alert,scanner,onerror,prompt,atestu,object,and ,having";

        //2、GET模式非法字符定义
        //双斜杠编码后为 %0a,故需要加上
        //此时为GET提交，否则为POST提交(POST提交，营销业务中存在参数中含HTML标签)
        if (req.getQueryString() != null) {
            //inj_str = "(function(,script,iframe,insert,delete,update,declare,expression,XSS,alert,scanner,onerror,prompt,atestu,object,and ,having,eval,{,},../,./,<!--,-->";
            inj_str = "(function(,script,iframe,insert,delete,update,declare,expression,XSS,alert,scanner,onerror,prompt,atestu,object,and ,having,eval,../,./,<!--,-->";
        }

        //get方式传递的参数判断
        String loginFrom = req.getRequestURL().toString();
        //转码
        loginFrom = URLDecoder.decode(loginFrom, "UTF-8");

        //需要对	URL中的参数进行转码
        String urlQuery = req.getQueryString() == null ? "" : URLDecoder.decode(req.getQueryString(), "UTF-8");
//		if (urlQuery.contains("SAMLart")) {//SSO放过
//			return true;
//		}

        String inj_stra[] = inj_str.split(",");
        for (int i = 0; i < inj_stra.length; i++) {
            if (urlQuery.toLowerCase().indexOf(inj_stra[i]) >= 0) {
                logger.info("校验不通过,原因,包含非法字符:" + inj_stra[i]);
                return false;
            }
        }

        //post 表单 和 ajax提交 参数处理
        Map map = req.getParameterMap();
        Set set = map.keySet();
        Iterator it = set.iterator();
        while (it.hasNext()) {
            String temp = (String) it.next();
            String value = "";
            if (map.get(temp) != null) {
                String[] values = (String[]) map.get(temp);
                value = values[0];
            }

            for (int i = 0; i < inj_stra.length; i++) {
                if (value.toLowerCase().indexOf(inj_stra[i]) >= 0) {
                    logger.info("校验不通过,原因,包含非法字符:" + inj_stra[i]);
                    return false;
                }
            }
        }

        return true;
    }

    private void errorResponse(HttpServletResponse response) throws IOException {
        response.setContentType("text/plain; charset=utf-8");
        PrintWriter out = response.getWriter();

        String retStr = "{\"tip\":" + "\"输入项中不能包含非法字符。\"" + ",\"mark\":\"202}";
        out.println(retStr);
        out.flush();
        out.close();
    }

    private void errorResponse(HttpServletResponse response, RetInfo retInfo) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        String json = JsonUtil.toJson(retInfo);
        logger.info(json);
        out.println(json);
        out.flush();
        out.close();
    }

    public void init(FilterConfig arg0) throws ServletException {

    }


    private void printHttpHeader(HttpServletRequest request) throws IOException {
        SystemAccessLog pojo = new SystemAccessLog();
        Enumeration headerNames = request.getHeaderNames();
        String uri = request.getRequestURI();
        String method = request.getMethod();
        String clientIp = Ip.getIpAddr(request);
        String msg = new String("\n----------------  Http Header [" + clientIp + "]---------------\n");
        msg = msg + method + " " + uri + "\n";
        pojo.setCip(clientIp);
        pojo.setUrl(uri);
        pojo.setCreate_date(new Date());

        StringBuffer header = new StringBuffer();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            header.append(key + ":" + value + "\n");
        }
        pojo.setHeader(header.toString());
        header.append("\n");
        msg = msg + header.toString();
        logger.info(msg);

        systemAccessLogDao.insertSelective(pojo);

    }

}
